use crate::{mm::memblock::MemblockRegion, vm::addr::paddr::Paddr};

use super::{MemblockFlags, MemblockType};

#[inline]
pub fn memblock_cap_size(base: Paddr, size: &mut usize) -> usize {
    *size = (*size).min(usize::MAX - base.to_value());
    *size
}

pub(super) fn memblock_insert_region(
    ty: &mut MemblockType,
    idx: usize,
    base: Paddr,
    size: usize,
    flags: MemblockFlags,
) {
    debug_assert!(ty.cnt < ty.max);
    unsafe {
        let dst = &mut ty.regions[idx + 1] as *mut MemblockRegion;
        dst.copy_from(&ty.regions[idx], ty.cnt - idx);
    }
    let rgn = &mut ty.regions[idx];
    rgn.base = base;
    rgn.size = size;
    rgn.flags = flags;
    ty.cnt += 1;
    ty.total_size += size;
}

pub(super) fn memblock_isolate_range(
    ty: &mut MemblockType,
    base: Paddr,
    mut size: usize,
    start_idx: &mut i32,
    end_idx: &mut i32,
) -> Result<(), ()> {
    let end = base + memblock_cap_size(base, &mut size);

    *start_idx = 0;
    *end_idx = 0;

    if size == 0 {
        return Ok(());
    }

    if ty.cnt + 2 > ty.max {
        return Err(());
    }

    let mut idx = 0;
    while idx < ty.cnt {
        let rgn = ty.regions[idx];
        let rbase = rgn.base;
        let rend = rbase + rgn.size;

        if rbase >= end {
            break;
        }
        if rend <= base {
            idx += 1;
            continue;
        }
        if rbase < base {
            {
                let rgn = &mut ty.regions[idx];
                rgn.base = base;
                rgn.size -= (base - rbase).to_value();
            }
            ty.total_size -= (base - rbase).to_value();
            memblock_insert_region(ty, idx, rbase, (base - rbase).to_value(), rgn.flags);
        } else if rend > end {
            {
                let rgn = &mut ty.regions[idx];
                rgn.base = end;
                rgn.size -= (end - rbase).to_value();
            }
            ty.total_size -= (end - rbase).to_value();
            memblock_insert_region(ty, idx, rbase, (end - rbase).to_value(), rgn.flags);
            idx -= 1;
        } else {
            if *end_idx == 0 {
                *start_idx = idx as i32;
            }
            *end_idx = idx as i32 + 1;
        }
        idx += 1;
    }
    Ok(())
}

pub(super) fn memblock_remove_region(ty: &mut MemblockType, i: usize) {
    ty.total_size -= ty.regions[i].size;
    unsafe {
        let dst = &mut ty.regions[i] as *mut MemblockRegion;
        dst.copy_from(&ty.regions[i + 1], ty.cnt - i - 1);
    }
    ty.cnt -= 1;
    if ty.cnt == 0 {
        debug_assert_eq!(ty.total_size, 0);
        ty.cnt = 1;
        ty.regions[0].base = Paddr::from(0);
        ty.regions[0].size = 0;
        ty.regions[0].flags = MemblockFlags::MEMBLOCK_NONE;
    }
}

pub(super) fn memblock_remove_range(
    ty: &mut MemblockType,
    base: Paddr,
    size: usize,
) -> Result<(), ()> {
    let mut start_idx = 0;
    let mut end_idx = 0;

    memblock_isolate_range(ty, base, size, &mut start_idx, &mut end_idx)?;

    for i in (start_idx..end_idx).rev() {
        memblock_remove_region(ty, i as usize);
    }
    Ok(())
}

pub(super) fn memblock_merge_regions(ty: &mut MemblockType) {
    let mut i = 0;

    while i < ty.cnt - 1 {
        let next = &ty.regions[i + 1];
        let this = &ty.regions[i];

        if this.base + this.size != next.base || this.flags != next.flags {
            assert!(this.base + this.size <= next.base);
            i += 1;
            continue;
        }

        unsafe {
            let unthis = this as *const MemblockRegion as *mut MemblockRegion;
            (*unthis).size += next.size;

            let dst = next as *const MemblockRegion as *mut MemblockRegion;
            dst.copy_from(&ty.regions[i + 2], ty.cnt - i - 2);
        }
        ty.cnt -= 1;
    }
}
